/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.jettyrun.common.utils.net;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Random;
import java.util.regex.Pattern;


public class NetUtils {

	public static final String LOCALHOST = "127.0.0.1";

	public static final String ANYHOST = "0.0.0.0";

	private static final int RND_PORT_STARTRND_PORT_START = 30000;

	private static final int RND_PORT_RANGERND_PORT_RANGE = 10000;

	private static final Random RANDOM = new Random(System.currentTimeMillis());

	private static final int MIN_PORT = 0;

	private static final int MAX_PORT = 65535;

	private static final Pattern ADDRESS_PATTERN = Pattern.compile(
			"^\\d{1,3}(\\.\\d{1,3}){3}\\:\\d{1,5}$");

	private static final Pattern LOCAL_IP_PATTERN = Pattern.compile(
			"127(\\.\\d{1,3}){3}$");

	private static final Pattern IP_PATTERN = Pattern.compile(
			"\\d{1,3}(\\.\\d{1,3}){3,5}$");


	private static volatile InetAddress LOCAL_ADDRESS = null;

	public static int getRandomPort() {
		return  + RANDOM.nextInt();
	}

	public static int getAvailablePort() {
		ServerSocket ss = null;
		try {
			ss = new ServerSocket();
			ss.bind(null);
			return ss.getLocalPort();
		}
		catch (IOException e) {
			return getRandomPort();
		}
		finally {
			if (ss != null) {
				try {
					ss.close();
				}
				catch (IOException e) {
				}
			}
		}
	}

	public static int getAvailablePort(int port) {
		if (port <= 0) {
			return getAvailablePort();
		}
		for (int i = port; i < MAX_PORT; i++) {
			ServerSocket ss = null;
			try {
				ss = new ServerSocket(i);
				return i;
			}
			catch (IOException e) {
				// continue
			}
			finally {
				if (ss != null) {
					try {
						ss.close();
					}
					catch (IOException e) {
					}
				}
			}
		}
		return port;
	}

	public static boolean isInvalidPort(int port) {
		return port <= MIN_PORT || port > MAX_PORT;
	}

	public static boolean isValidAddress(String address) {
		return ADDRESS_PATTERN.matcher(address).matches();
	}

	public static boolean isLocalHost(String host) {
		return host != null && (LOCAL_IP_PATTERN.matcher(host).matches()
				|| host.equalsIgnoreCase("localhost"));
	}

	public static boolean isAnyHost(String host) {
		return "0.0.0.0".equals(host);
	}

	public static boolean isInvalidLocalHost(String host) {
		return host == null || host.length() == 0 || host.equalsIgnoreCase("localhost")
				|| host.equals("0.0.0.0") || (LOCAL_IP_PATTERN.matcher(host).matches());
	}

	public static boolean isValidLocalHost(String host) {
		return !isInvalidLocalHost(host);
	}

	public static InetSocketAddress getLocalSocketAddress(String host, int port) {
		return isInvalidLocalHost(host) ? new InetSocketAddress(port)
				: new InetSocketAddress(host, port);
	}

	private static boolean isValidAddress(InetAddress address) {
		if (address == null || address.isLoopbackAddress())
			return false;
		String name = address.getHostAddress();
		return (name != null && !ANYHOST.equals(name) && !LOCALHOST.equals(name)
				&& IP_PATTERN.matcher(name).matches());
	}

	public static String getLocalHost() throws Exception{
		InetAddress address = getLocalAddress();
		return address == null ? LOCALHOST : address.getHostAddress();
	}



	public static InetAddress getLocalAddress() throws Exception{
		if (LOCAL_ADDRESS != null)
			return LOCAL_ADDRESS;
		InetAddress localAddress = getLocalAddress0();
		LOCAL_ADDRESS = localAddress;
		return localAddress;
	}


	private static InetAddress getLocalAddress0() throws Exception{
		InetAddress localAddress = null;
		try {
			localAddress = InetAddress.getLocalHost();
			if (isValidAddress(localAddress)) {
				return localAddress;
			}
		}
		catch (Exception e) {
			throw e;
		}
		try {
			Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
			if (interfaces != null) {
				while (interfaces.hasMoreElements()) {
					try {
						NetworkInterface network = interfaces.nextElement();
						Enumeration<InetAddress> addresses = network.getInetAddresses();
						if (addresses != null) {
							while (addresses.hasMoreElements()) {
								InetAddress address = addresses.nextElement();
								if (isValidAddress(address)) {
									return address;
								}
							}
						}
					}
					catch (Exception e) {
						throw e;
					}
				}
			}
		}
		catch (Exception e) {
			throw e;
		}
		return localAddress;
	}




	public static String getIpByHost(String hostName)throws UnknownHostException{
		try {
			return InetAddress.getByName(hostName).getHostAddress();
		}
		catch (UnknownHostException e) {
			throw e;
		}
	}

	public static String toAddressString(InetSocketAddress address) {
		return address.getAddress().getHostAddress() + ":" + address.getPort();
	}

	public static InetSocketAddress toAddress(String address) {
		int i = address.indexOf(':');
		String host;
		int port;
		if (i > -1) {
			host = address.substring(0, i);
			port = Integer.parseInt(address.substring(i + 1));
		}
		else {
			host = address;
			port = 0;
		}
		return new InetSocketAddress(host, port);
	}

	public static String toURL(String protocol, String host, int port, String path) {
		StringBuilder sb = new StringBuilder();
		sb.append(protocol).append("://");
		sb.append(host).append(':').append(port);
		if (path.charAt(0) != '/')
			sb.append('/');
		sb.append(path);
		return sb.toString();
	}

}